/*
 * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"

#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_log.h"
#include "mqtt_client.h"
#include "bsp_mqtt.h"

static const char *TAG = "bsp.mqtt5";

#define CONFIG_BROKER_URL "mqtt://192.168.3.193"

typedef struct
{
    uint32_t payloadLen; // 数据长度
    uint32_t topicLen;   // topic长度
    char *topic;         // 主题信息
    char *payload;       // 数据内容
} MqttMsgData_t;

static char localIp[20];
static uint8_t localMac[6];
static esp_mqtt_client_handle_t s_client;

// 消息队列控制权柄
QueueHandle_t mqttMsgQueue;
#define MQTT_QUEUE_SIZE 100

// from c++
void mqttSubscribeList(const char *localIp, uint8_t *localMac);
void handleMainProtocolParse(const char *topic, const char *payload, uint32_t payloadLen);

void mqtt_message_handler(void *parameter)
{
    MqttMsgData_t msg;

    while (1)
    {
        // 从消息队列中接收消息
        xQueueReceive(mqttMsgQueue, &msg, portMAX_DELAY);

        // 处理接收到的MQTT消息
        // 这里可以根据msg的内容进行不同的处理

        ESP_LOGI(TAG, "Received MQTT topic:%s", msg.topic);
        ESP_LOGI(TAG, "Received MQTT payload:%s", msg.payload);
        ESP_LOGI(TAG, "payloadLen:%d", (int)msg.payloadLen);

        handleMainProtocolParse(msg.topic, msg.payload, msg.payloadLen);

        // 释放负载内存
        free(msg.topic);
        free(msg.payload);
    }
}

int mqtt_handler_init()
{
    // 任务控制权柄
    TaskHandle_t xHandleTsak;

    // 创建消息队列
    mqttMsgQueue = xQueueCreate(MQTT_QUEUE_SIZE, sizeof(MqttMsgData_t));
    if (mqttMsgQueue != NULL)
    {
        // 线程优先级要比mqtt线程低一级，要优先接收mqtt消息放入队列，再处理队列中消息
        // #define MQTT_TASK_PRIORITY          5
        xTaskCreate(
            (TaskFunction_t)mqtt_message_handler,
            (const char *)"mqtt_message_handler", (uint16_t)1024,
            (void *)NULL, 6, &xHandleTsak);
    }

    return 0;
}

static void log_error_if_nonzero(const char *message, int error_code)
{
    if (error_code != 0)
    {
        ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
    }
}

static esp_mqtt5_user_property_item_t user_property_arr[] = {
    {"board", "esp32"},
    {"u", "user"},
    {"p", "password"}};

#define USE_PROPERTY_ARR_SIZE sizeof(user_property_arr) / sizeof(esp_mqtt5_user_property_item_t)

static esp_mqtt5_disconnect_property_config_t disconnect_property = {
    .session_expiry_interval = 60,
    .disconnect_reason = 0,
};

static void print_user_property(mqtt5_user_property_handle_t user_property)
{
    if (user_property)
    {
        uint8_t count = esp_mqtt5_client_get_user_property_count(user_property);
        if (count)
        {
            esp_mqtt5_user_property_item_t *item = malloc(count * sizeof(esp_mqtt5_user_property_item_t));
            if (esp_mqtt5_client_get_user_property(user_property, item, &count) == ESP_OK)
            {
                for (int i = 0; i < count; i++)
                {
                    esp_mqtt5_user_property_item_t *t = &item[i];
                    ESP_LOGI(TAG, "key is %s, value is %s", t->key, t->value);
                    free((char *)t->key);
                    free((char *)t->value);
                }
            }
            free(item);
        }
    }
}

// 深入调研MQTT v5.0及其新特性 https://www.jianshu.com/p/f4f50821eb98
void mqttSubscribe(const char *topic)
{
    // esp_mqtt5_subscribe_property_config_t subscribe_property = {
    //     .subscribe_id = 25555,
    //     .no_local_flag = false, // 而在 MQTT v5 中，如果你在订阅时将此选项设置为 1，那么服务端将不会向你转发你自己发布的消息
    //     .retain_as_published_flag = false,
    //     .retain_handle = 0,
    //     .is_share_subscribe = false,
    // };

    // esp_mqtt5_client_set_user_property(&subscribe_property.user_property, user_property_arr, USE_PROPERTY_ARR_SIZE);
    // esp_mqtt5_client_set_subscribe_property(s_client, &subscribe_property);
    int msg_id = esp_mqtt_client_subscribe(s_client, topic, MqttQos0);
    // esp_mqtt5_client_delete_user_property(subscribe_property.user_property);
    // subscribe_property.user_property = NULL;
    ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
}

void mqttUnSubscribe(const char *topic)
{
    // esp_mqtt5_unsubscribe_property_config_t unsubscribe_property = {
    //     .is_share_subscribe = false,
    //     .share_name = "group1",
    // };

    // esp_mqtt5_client_set_user_property(&unsubscribe_property.user_property, user_property_arr, USE_PROPERTY_ARR_SIZE);
    // esp_mqtt5_client_set_unsubscribe_property(s_client, &unsubscribe_property);
    int msg_id = esp_mqtt_client_unsubscribe(s_client, "/topic/qos0");
    // esp_mqtt5_client_delete_user_property(unsubscribe_property.user_property);
    // unsubscribe_property.user_property = NULL;

    ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
}

int mqttPublish(const char *topic, const char *payload)
{
    // esp_mqtt5_publish_property_config_t publish_property = {
    //     .payload_format_indicator = 1,
    //     .message_expiry_interval = 1000,
    //     .topic_alias = 0,
    // };

    // esp_mqtt5_client_set_user_property(&publish_property.user_property, user_property_arr, USE_PROPERTY_ARR_SIZE);
    // esp_mqtt5_client_set_publish_property(s_client, &publish_property);
    int msg_id = esp_mqtt_client_publish(s_client, (char *)topic, (char *)payload, 0, MqttQos0, 0);
    // esp_mqtt5_client_delete_user_property(publish_property.user_property);
    // publish_property.user_property = NULL;
    ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);

    return 0;
}

int mqttPublishRetain(const char *topic, const char *payload)
{
    // esp_mqtt5_publish_property_config_t publish_property = {
    //     .payload_format_indicator = 1,
    //     .message_expiry_interval = 1000,
    //     .topic_alias = 0,
    //     .response_topic = "/topic/test/response",
    //     .correlation_data = "123456",
    //     .correlation_data_len = 6,
    // };
    // esp_mqtt5_client_set_user_property(&publish_property.user_property, user_property_arr, USE_PROPERTY_ARR_SIZE);
    // esp_mqtt5_client_set_publish_property(s_client, &publish_property);
    int msg_id = esp_mqtt_client_publish(s_client, (char *)topic, (char *)payload, 0, MqttQos0, 1);
    // esp_mqtt5_client_delete_user_property(publish_property.user_property);
    // publish_property.user_property = NULL;
    ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
    return 0;
}

int mqttConnectedCallback()
{
    esp_netif_t *intf = esp_netif_get_default_netif();
    esp_netif_ip_info_t info;
    esp_netif_get_ip_info(intf, &info);
    strcpy(localIp, inet_ntoa(info.ip.addr));
    esp_netif_get_mac(intf, localMac);

    ESP_LOGI(TAG, "IP:%s", localIp);
    ESP_LOGI(TAG, "MAC:%02X:%02X:%02X:%02X:%02X:%02X", localMac[0], localMac[1],
             localMac[2], localMac[3], localMac[4], localMac[5]);

    mqttSubscribeList(localIp, localMac);

    return 0;
}

void mqttDisconnected()
{
    esp_mqtt5_client_set_user_property(&disconnect_property.user_property, user_property_arr, USE_PROPERTY_ARR_SIZE);
    esp_mqtt5_client_set_disconnect_property(s_client, &disconnect_property);
    esp_mqtt5_client_delete_user_property(disconnect_property.user_property);
    disconnect_property.user_property = NULL;
    esp_mqtt_client_disconnect(s_client);
}

/*
 * @brief Event handler registered to receive MQTT events
 *
 *  This function is called by the MQTT s_client event loop.
 *
 * @param handler_args user data registered to the event.
 * @param base Event base for the handler(always MQTT Base in this example).
 * @param event_id The id for the received event.
 * @param event_data The data for the event, esp_mqtt_event_handle_t.
 */
static void mqtt5_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32, base, event_id);
    esp_mqtt_event_handle_t event = event_data;

    ESP_LOGD(TAG, "free heap size is %" PRIu32 ", minimum %" PRIu32, esp_get_free_heap_size(), esp_get_minimum_free_heap_size());
    switch ((esp_mqtt_event_id_t)event_id)
    {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        mqttConnectedCallback();

        print_user_property(event->property->user_property);

        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");

        print_user_property(event->property->user_property);
        break;
    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
        print_user_property(event->property->user_property);

        break;
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        print_user_property(event->property->user_property);

        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        print_user_property(event->property->user_property);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
        print_user_property(event->property->user_property);

        if (mqttMsgQueue != NULL)
        {
            MqttMsgData_t msg;

            msg.topic = malloc(event->topic_len + 1);
            memset(msg.topic, '\0', event->topic_len + 1);

            if (msg.topic != NULL)
            {
                // 复制topic
                memcpy(msg.topic, event->topic, event->topic_len);
                msg.topicLen = event->topic_len;
            }
            else
            {
                ESP_LOGI(TAG, "msg.topic malloc error");
                return;
            }

            // 为负载分配内存
            msg.payload = malloc(event->data_len + 1);
            memset(msg.payload, '\0', event->data_len + 1);

            if (msg.payload != NULL)
            {
                // 复制负载数据
                memcpy(msg.payload, event->data, event->data_len);
                msg.payloadLen = event->data_len;
            }
            else
            {
                ESP_LOGI(TAG, "msg.payload malloc error");
                return;
            }

            xQueueSend(mqttMsgQueue, &msg, 0);
        }
        else
        {
            ESP_LOGI(TAG, "payload_format_indicator is %d", event->property->payload_format_indicator);
            ESP_LOGI(TAG, "response_topic is %.*s", event->property->response_topic_len, event->property->response_topic);
            ESP_LOGI(TAG, "correlation_data is %.*s", event->property->correlation_data_len, event->property->correlation_data);
            ESP_LOGI(TAG, "content_type is %.*s", event->property->content_type_len, event->property->content_type);

            ESP_LOGI(TAG, "TOPIC=%.*s", event->topic_len, event->topic);
            ESP_LOGI(TAG, "DATA=%.*s", event->data_len, event->data);
        }

        break;
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        print_user_property(event->property->user_property);
        ESP_LOGI(TAG, "MQTT5 return code is %d", event->error_handle->connect_return_code);
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
        {
            log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
            ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
        }
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}

static void mqtt5_app_start(void)
{
    esp_mqtt5_connection_property_config_t connect_property = {
        .session_expiry_interval = 10,
        .maximum_packet_size = 1000,
        .receive_maximum = 65535,
        .topic_alias_maximum = 2,
        .request_resp_info = true,
        .request_problem_info = true,
        .will_delay_interval = 10,
        .payload_format_indicator = true,
        .message_expiry_interval = 10,
        .response_topic = "/connect/response",
        .correlation_data = "123456",
        .correlation_data_len = 6,
    };

    esp_mqtt_client_config_t mqtt5_cfg = {
        .broker.address.uri = CONFIG_BROKER_URL,
        .session.protocol_ver = MQTT_PROTOCOL_V_5,
        .network.disable_auto_reconnect = false,
        .credentials.username = "esp32",
        .credentials.authentication.password = "123456",
        .session.last_will.topic = "/topic/will",
        .session.last_will.msg = "i will leave",
        .session.last_will.msg_len = 12,
        .session.last_will.qos = 1,
        .session.last_will.retain = true,
    };

    s_client = esp_mqtt_client_init(&mqtt5_cfg);

    /* Set connection properties and user properties */
    esp_mqtt5_client_set_user_property(&connect_property.user_property, user_property_arr, USE_PROPERTY_ARR_SIZE);
    esp_mqtt5_client_set_user_property(&connect_property.will_user_property, user_property_arr, USE_PROPERTY_ARR_SIZE);
    esp_mqtt5_client_set_connect_property(s_client, &connect_property);

    /* If you call esp_mqtt5_client_set_user_property to set user properties, DO NOT forget to delete them.
     * esp_mqtt5_client_set_connect_property will malloc buffer to store the user_property and you can delete it after
     */
    esp_mqtt5_client_delete_user_property(connect_property.user_property);
    esp_mqtt5_client_delete_user_property(connect_property.will_user_property);

    /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
    esp_mqtt_client_register_event(s_client, ESP_EVENT_ANY_ID, mqtt5_event_handler, NULL);
    esp_mqtt_client_start(s_client);
}

void mqtt5_init(void)
{
    ESP_LOGI(TAG, "[MQTT] Startup..");
    ESP_LOGI(TAG, "[MQTT] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
    ESP_LOGI(TAG, "[MQTT] IDF version: %s", esp_get_idf_version());

    esp_log_level_set("*", ESP_LOG_INFO);
    esp_log_level_set("mqtt_client", ESP_LOG_VERBOSE);
    esp_log_level_set("bsp.mqtt", ESP_LOG_VERBOSE);
    esp_log_level_set("transport_base", ESP_LOG_VERBOSE);
    esp_log_level_set("esp-tls", ESP_LOG_VERBOSE);
    esp_log_level_set("transport", ESP_LOG_VERBOSE);
    esp_log_level_set("outbox", ESP_LOG_VERBOSE);

    mqtt5_app_start();
    mqtt_handler_init();
}